package org.maxgroundstation.groundstation.comm.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * A class that handles receiving service TCP communication.
 * @author Scott Davis
 * 
 * Copyright (C) 2010  Scott Davis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class ServiceReceiverTCP implements Runnable {
    
    // Data members
    private Thread receiverThread;
    private ServiceCommunicationTCP comm;
    private Map<String, Long> currentTimestamps;
    private Map<String, ServiceDataFrame> dataFrames;
    private boolean run = false;
    private InputStream in;
	
    /**
     * Constructor
     * @param comm the service TCP communication.
     */
    ServiceReceiverTCP(ServiceCommunicationTCP comm) {
    	this.comm = comm;
    	
    	currentTimestamps = new HashMap<String, Long>();
        dataFrames = new HashMap<String, ServiceDataFrame>();
        in = null;
    }
    
    /**
     * Initializes the input data stream from the socket.
     * @throws IOException if error initializing the input stream.
     */
    void initializeInputDataStream() throws IOException {
    	if (comm.getSocket().getInputStream() == null) {
    		System.err.println("TCP input stream is null");
    	}
    	
    	in = comm.getSocket().getInputStream();
    }
    
    /**
     * Closes the input data stream.
     * @throws IOException if error closing the input stream.
     */
    void closeInputDataStream() throws IOException {
    	//System.out.println("Closing service receiver TCP stream.");
    	if (in != null) in.close();
    	in = null;
    }
    
    void setRun(boolean run) {
    	this.run = run;
    }
    
    /**
     * Starts the TCP receiver thread.
     */
    void start() {
    	//System.out.println("Starting TCP thread 1");
        if ((receiverThread == null) || !receiverThread.isAlive() || !run) {
        	//System.out.println("Starting TCP thread 2");
        	receiverThread = new Thread(this, "tcp receiver thread");
        	receiverThread.start();
        }
    }
    
	@Override
	public void run() {
		run = true;
		while (run) {
			//System.out.println("Service TCP receiver running");
			if (comm.isConnected()) {
				try {
					// Load fragment header data.
					byte[] headerData = new byte[40];
					
					int read = 0;
					while ((read < headerData.length) && comm.isConnected()) {
						int newread = 0;
						try {
							//System.out.println("Before in.read in TCP receiver");
							newread = in.read(headerData, read, (headerData.length - read));
							//System.out.println("Reading " + newread + " bytes from TCP receiver input stream");
						}
						catch (Exception e) {
							System.err.println("Service receiver TCP header error: " + e.getMessage());
						}
						if (newread == -1) throw new IOException("End Of Stream");
						read += newread;
					}
					
					if (!comm.isConnected()) continue;
					
					//System.out.println("Receiving TCP header");
					
					// Create data fragment.
					ServiceDataFragment dataFrag = new ServiceDataFragment();
					dataFrag.setHeader(ServiceUtil.parseDataFragmentHeader(headerData));
					comm.getServiceCommunication().setClientID(dataFrag.getHeader().getClientID());
					
					// Load fragment data.
					long frameLength = dataFrag.getHeader().getFrameLength();
					int fragSize = dataFrag.getHeader().getFragSize();
					int fragNumber = dataFrag.getHeader().getFragNumber();
					int totalFragNum = (int) Math.ceil((double) frameLength / (double) fragSize);
					int imageDataLength = fragSize;
	                if (fragNumber == (totalFragNum - 1)) {
	                	long totalFragLength = ((long) totalFragNum * (long) fragSize); 
	                	if (totalFragLength > frameLength) {
	                		long lesserFragLength = ((long) (totalFragNum - 1) * (long) fragSize);
	                		imageDataLength = (int) (frameLength - lesserFragLength);
	                	}
	                }
					byte[] fragmentData = new byte[imageDataLength];
					read = 0;
					while (read < fragmentData.length) {
						int newread = 0;
						try {
							newread = in.read(fragmentData, read, (fragmentData.length - read));
						}
						catch (Exception e) {
							System.err.println("Service receiver TCP fragment error: " + e.getMessage());
						};
						if (newread == -1) throw new IOException("End Of Stream");
						read += newread;
					}
					dataFrag.setData(fragmentData);
					
					String serviceID = dataFrag.getHeader().getServiceID();
					
					long currentTimestamp = Long.MIN_VALUE;
            		if (currentTimestamps.containsKey(serviceID)) currentTimestamp = currentTimestamps.get(serviceID);
					long timestamp = dataFrag.getHeader().getTimestamp();
            		if (timestamp > currentTimestamp) {
            			currentTimestamps.put(serviceID, timestamp);
            			dataFrames.put(serviceID, new ServiceDataFrame(ServiceCommunication.TCP));
            		}
            		else if (timestamp < currentTimestamp) {
            			System.err.println("Old packet timestamp");
            			continue;
            		}
            		
            		//System.out.println("Receiving TCP packet size: " + dataFrag.getData().length + 40);
            		
            		if (dataFrames.containsKey(serviceID)) {
            			ServiceDataFrame dataFrame = dataFrames.get(serviceID);
            			dataFrame.addDataFragment(dataFrag);
            			
            			if (dataFrame.isDataComplete()) {
            				//System.out.println("Receiving full data:");
                			//System.out.println(dataFrame.getDataString());
                			comm.getServiceCommunication().handleResponseData(dataFrame);
                			
                			dataFrames.remove(serviceID);
            			}
            		}
				}
				catch(IOException e) {
					System.err.println("Error receiving TCP service: " + e.getMessage());
					e.printStackTrace(System.err);
				}
			}
			else {
				System.out.println("Service TCP Socket not connected");
			}
		}
	}
}